package br.edu.ufcg.unknown.semantico.arvore;

import br.edu.ufcg.unknown.exception.AnalisadorSemanticoUnknownRuntimeException;
import br.edu.ufcg.unknown.geradorDeCodigo.GeradorDeCodigo;
import br.edu.ufcg.unknown.lexico.Token;
import br.edu.ufcg.unknown.semantico.Tipo;
import br.edu.ufcg.unknown.sintatico.IdentificaTK;

public class GeradorDeArvores {

	private Nodo nodoRaiz;

	private Nodo ultimoOperador;

	private Nodo ultimoNodoTerminal;

	private GeradorDeCodigo gCodigo = GeradorDeCodigo.getInstance();

	/**
	 * Linha onde se encontra o tipo sendo esperado na avaliação.
	 */
	private int linha = Integer.MAX_VALUE;

	/**
	 * Coluna onde se encontra o tipo sendo esperado na avaliação.
	 */
	private int coluna = Integer.MAX_VALUE;

	private static final IdentificaTK ident = new IdentificaTK();

	public void addNodo(Nodo nodo) {
		// 1a regra
		if (this.nodoRaiz == null) {
			setNodoRaiz(nodo);
			setUltimo(nodo);
			return;
		}

		// 3a regra
		if ((nodo.getTipo().getToken().ehOperadorSemantico()) && ultimoOperador == null
				&& (ultimoNodoTerminal != null)) {
			nodo.setFilhoEsquerda(ultimoNodoTerminal);
			setUltimo(nodo);
			setNodoRaiz(nodo);
			return;
		}

		// 2a regra
		if (nodo.getTipo().getToken().ehTerminal() && (ultimoOperador != null)) {

			ultimoOperador.setFilhoDireita(nodo);
			setUltimo(nodo);
			return;
		}

		// 4a regra
		if ((nodo.getTipo().getToken().ehOperadorSemantico()) && (ultimoOperador != null)) {

			if (nodo.getTipo().getPrecedencia() <= nodoRaiz.getTipo().getPrecedencia()) {
				nodo.setFilhoEsquerda(nodoRaiz); // Operador olha a raiz
				setNodoRaiz(nodo);
			} else {
				insiraNaDireita(nodoRaiz, nodo);
			}
			setUltimo(nodo);
		}

	}

	private void insiraNaDireita(Nodo nodoAtual, Nodo nodoNovo) {

		if ((nodoAtual.getFilhoDireita() == null || nodoAtual.getFilhoDireita().ehFolha() || (nodoAtual
				.getFilhoDireita().getTipo().getPrecedencia() > nodoNovo.getTipo().getPrecedencia()))) {
			Nodo aux = nodoAtual.getFilhoDireita();
			nodoAtual.setFilhoDireita(nodoNovo);
			nodoNovo.setFilhoEsquerda(aux);
			return;
		}

		if (nodoAtual.getTipo().getPrecedencia() <= nodoNovo.getTipo().getPrecedencia()) {
			insiraNaDireita(nodoAtual.getFilhoDireita(), nodoNovo);
			return;
		}

		if (nodoAtual.getTipo().getPrecedencia() > nodoNovo.getTipo().getPrecedencia()) {
			Nodo aux = nodoAtual.getFilhoDireita();
			nodoAtual.setFilhoDireita(nodoNovo);
			nodoNovo.setFilhoEsquerda(aux);
			return;
		}

	}

	private void setUltimo(Nodo nodo) {
		if (nodo.getTipo().getToken().ehOperadorSemantico()) {
			this.ultimoOperador = nodo;
		} else {
			this.ultimoNodoTerminal = nodo;
		}

	}

	public Nodo getRaiz() {
		return this.nodoRaiz;
	}

	public void setNodoRaiz(Nodo nodo) {
		this.nodoRaiz = nodo;
		if (nodo != null && nodo.getTipo() != null && nodo.getTipo().getToken() != null) {
			Token tk = nodo.getTipo().getToken();
			if (tk.getLinha() < this.linha) {
				this.linha = tk.getLinha();
				this.coluna = tk.getColuna();
			} else if (tk.getLinha() == this.linha) {
				this.coluna = Math.min(this.coluna, tk.getColuna());
			}
		}
	}

	public Nodo resolver() {

		if (this.nodoRaiz == null) {
			return null;
		}

		Nodo pai = this.nodoRaiz;
		Nodo no;
		String temp = "";
		if (this.nodoRaiz.esq == null && this.nodoRaiz.dir == null) {
			temp = this.gCodigo.makeTemp();
			no = new Nodo(new Token(pai.getTipo().getTipo(), "t_ident", "t_boolean", true,
					this.nodoRaiz.getTipo().getToken().getLinha(), this.nodoRaiz.getTipo()
							.getToken().getColuna()), true);
			no.setConteudo(temp);
			this.gCodigo.emitir(temp, pai.getConteudo());
		} else {
			no = analisa(pai);
		}

		if (no != null) {
			gCodigo.pushPcO(no.getConteudo());
		}

		return no;
	}

	private Nodo analisa(Nodo pai) {

		if (pai != null) {
			if (pai.esq != null && pai.dir != null && pai.esq.ehFolha() && pai.dir.ehFolha())
				return compara(pai.esq, pai.dir, pai);
			else if (pai.esq != null && pai.esq.ehFolha() && pai.dir == null)
				return compara(pai.esq, analisa(pai.dir), pai);
			else if (pai.esq == null && pai.dir != null && pai.dir.ehFolha())
				return compara(analisa(pai.esq), pai.dir, pai);
			else
				return compara(analisa(pai.esq), analisa(pai.dir), pai);
		}
		return null;
	}

	private Nodo compara(Nodo n1, Nodo n2, Nodo pai)
			throws AnalisadorSemanticoUnknownRuntimeException {

		Token op = pai.getTipo().getToken();
		String temp = "";
		Nodo no = null;

		// caso seja unário
		if (n1 != null && n2 == null)
			return compara(n1, op);

		if (n1 == null && n2 != null)
			return compara(n2, op);

		if (n1 == null && n2 == null) {
			return pai;
		}

		Tipo t1 = n1.getTipo();
		Tipo t2 = n2.getTipo();

		// caso binário
		if (t1.equals(t2)) {

			if (ident.eh_IGUAL(op) || ident.eh_DIFERENTE(op)) {
				temp = this.gCodigo.makeTemp();
				no = new Nodo(new Token("BOOLEAN", "t_ident", "t_boolean", true, pai.getTipo()
						.getToken().getLinha(), pai.getTipo().getToken().getColuna()), true);
				no.setConteudo(temp);
				this.gCodigo.emitir(temp, op.getLexema(), n1.getConteudo(), n2.getConteudo());
				return no;

			} else if (ident.eh_AND(op) || ident.eh_OR(op)) {
				if (t1.ehBoolean() && t2.ehBoolean()) {
					temp = this.gCodigo.makeTemp();
					no = new Nodo(new Token("BOOLEAN", "t_ident", "t_boolean", true, pai.getTipo()
							.getToken().getLinha(), pai.getTipo().getToken().getColuna()), true);
					no.setConteudo(temp);
					this.gCodigo.emitir(temp, op.getLexema(), n1.getConteudo(), n2.getConteudo());
					return no;
				} else {
					if (t1.ehBoolean())
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t2.getTipo() + " >> não esperado: " + op.getLexema());
					else
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t1.getTipo() + " >> não esperado: " + op.getLexema());

				}
			} else if (ident.eh_MAIOR(op) || ident.eh_MENOR(op) || ident.eh_MAIOR_IGUAL(op)
					|| ident.eh_MENOR_IGUAL(op)) {
				if (t1.ehInteger() && t2.ehInteger()) {
					temp = this.gCodigo.makeTemp();
					no = new Nodo(new Token("BOOLEAN", "t_ident", "t_boolean", true, pai.getTipo()
							.getToken().getLinha(), pai.getTipo().getToken().getColuna()), true);
					no.setConteudo(temp);
					this.gCodigo.emitir(temp, op.getLexema(), n1.getConteudo(), n2.getConteudo());
					return no;
				} else {

					if (t1.ehInteger())
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t2.getTipo() + " >> não esperado: " + op.getLexema());
					else
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t1.getTipo() + " >> não esperado: " + op.getLexema());
				}
			} else {

				if (t1.ehInteger() && t2.ehInteger()) {
					temp = this.gCodigo.makeTemp();
					no = new Nodo(new Token("INTEGER", "t_ident", "t_boolean", true, pai.getTipo()
							.getToken().getLinha(), pai.getTipo().getToken().getColuna()), true);
					no.setConteudo(temp);
					this.gCodigo.emitir(temp, op.getLexema(), n1.getConteudo(), n2.getConteudo());
					return no;
				} else {

					if (t1.ehInteger())
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t2.getTipo() + " >> não esperado: " + op.getLexema());
					else
						throw new AnalisadorSemanticoUnknownRuntimeException("Tipo << "
								+ t1.getTipo() + " >> não esperado: " + op.getLexema());
				}
			}
		} else {
			throw new AnalisadorSemanticoUnknownRuntimeException(t1, t2,
					this.linha == Integer.MAX_VALUE ? 0 : this.linha,
					this.coluna == Integer.MAX_VALUE ? 0 : this.coluna);
		}
	}

	private Nodo compara(Nodo n, Token op) {

		if (ident.eh_NOT(op)) {
			if (n.getTipo().ehBoolean())
				return new Nodo(new Token("BOOLEAN", "t_ident", "t_boolean", true, n.getTipo()
						.getToken().getLinha(), n.getTipo().getToken().getColuna()), true);
			else {
				// TODO lança erro
			}
		}
		return null;
	}

}
